package com.leigq.basal.redis.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import com.leigq.basaltools.util.JacksonUtils;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * Redis配置
 * <p>
 *
 * @author ：leigq <br>
 */
@Configuration
public class RedisConfig extends CachingConfigurerSupport {

    /**
     * 自定义缓存 key 的生成策略。默认的生成策略是看不懂的(乱码内容) 通过 Spring 的依赖注入特性进行自定义的配置注入并且此类是一个配置类可以更多程度的自定义配置
     * <br/>
     * 使用参考：
     * <ul>
     *     <li>
     *         <a href='http://blog.didispace.com/springbootcache2/'>Spring Boot中的缓存支持（二）使用Redis做集中式缓存</a>
     *     </li>
     *     <li>
     *         <a href='http://blog.didispace.com/springbootcache1/'>Spring Boot中的缓存支持（一）注解配置与EhCache使用</a>
     *     </li>
     * </ul>
     * <p>
     *
     * @return the key generator
     */
    @Bean(name = "basalRedisCacheKeyGenerator")
    @Override
    public KeyGenerator keyGenerator() {
        return (target, method, params) -> {
            StringBuilder sb = new StringBuilder();
            sb.append(target.getClass().getName());
            sb.append(method.getName());
            for (Object obj : params) {
                sb.append(obj.toString());
            }
            return sb.toString();
        };
    }

    /**
     * 缓存配置管理器
     *
     * @param redisConnectionFactory the redis connection factory
     * @return the cache manager
     */
    @Bean(name = "basalRedisCacheManager")
    public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
        // 以锁写入的方式创建RedisCacheWriter对象
        RedisCacheWriter writer = RedisCacheWriter.lockingRedisCacheWriter(redisConnectionFactory);

        // 设置 CacheManager 的 Value 序列化方式为 Jackson2JsonRedisSerialize, RedisCacheConfiguration 默认就是使用 StringRedisSerializer序列化key，
        // JdkSerializationRedisSerializer 序列化 value
        RedisSerializationContext.SerializationPair<Object> pair = RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer());
        // 创建默认缓存配置对象
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig().serializeValuesWith(pair);
        return new RedisCacheManager(writer, config);
    }

    /**
     * redisTemplate 序列化默认使用的 JdkSerializationRedisSerializer, 存储二进制字节码，这里改为使用 jackson2JsonRedisSerializer 自定义序列化
     * <br/>
     * StringRedisTemplate 使用的是 StringRedisSerializer，不受影响，不用重新配置
     *
     * @param redisConnectionFactory redis连接工厂
     * @return RedisTemplate
     */
    @Bean
    @Primary
    public RedisTemplate<Object, Object> getRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);

        // 使用 Jackson2JsonRedisSerialize 替换默认序列化
        // 以下代码为将 RedisTemplate 的 Value 序列化方式由 JdkSerializationRedisSerializer更换为 Jackson2JsonRedisSerializer
        // 此种序列化方式结果清晰、容易阅读、存储字节少、速度快，所以推荐更换
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer());
        // 设置 key 的序列化规则
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer());
        redisTemplate.setStringSerializer(new StringRedisSerializer());
        // 是否启用事务
        // redisTemplate.setEnableTransactionSupport(true);
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }


    /**
     * Jackson 2 json redis serializer jackson 2 json redis serializer.
     *
     * @return the jackson 2 json redis serializer
     */
    private Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer() {
        // 使用 Jackson2JsonRedisSerialize 替换默认序列化
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        jackson2JsonRedisSerializer.setObjectMapper(Holder.OBJECT_MAPPER);
        return jackson2JsonRedisSerializer;
    }


    private static class Holder {
        static final ObjectMapper OBJECT_MAPPER = JacksonUtils.CustomObjectMapper.newInstance();

        static {
            // 重写一些配置
            OBJECT_MAPPER.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
            // 序列化时允许非常量字段均输出类型 (此项必须配置，否则会报java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to XXX)
            OBJECT_MAPPER.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);

            OBJECT_MAPPER.disable(MapperFeature.USE_ANNOTATIONS);
            OBJECT_MAPPER.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
            OBJECT_MAPPER.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
            OBJECT_MAPPER.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        }
    }
}
